/*
 *                    COPYRIGHT NOTICE
 *
 *           This code is copyright protected by Freedom Inc.
 *
 *              2001-2011 All Rights Are Reserved
 */
package com.freedom.oldsong.spring;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;


/**
 * @author steven.wang
 *
 */
public final class GenericsUtils {
	private GenericsUtils(){
		
	}
	private static final Logger LOGGER = LoggerFactory.getLogger(GenericsUtils.class);

	/**
	 * Locates the first generic declaration on a class.
	 * 
	 * @param clazz
	 *            The class to introspect
	 * @return the first generic declaration, or <code>null</code> if cannot be
	 *         determined
	 * 
	 * @throws IllegalArgumentException
	 *             if clazz is null or not a parameterized type.
	 * 
	 * @see #getGenericClass(Class, int)
	 */
	/**
	 * @param clazz
	 * @return
	 * @throws IllegalArgumentException
	 */
	public static final Class<?> getGenericClass(final Class<?> clazz)
			throws IllegalArgumentException {
		return getGenericClass(clazz, 0);
	}

	/**
	 * Locates generic declaration by index on a class.
	 * 
	 * @param clazz
	 *            clazz The class to introspect
	 * @param index
	 *            the Index of the generic ddeclaration,start from 0.
	 * 
	 * @throws IllegalArgumentException
	 *             if clazz is null or not a parameterized type.
	 * @throws IndexOutOfBoundsException
	 *             if index is out of range.
	 */
	public static final Class<?> getGenericClass(final Class<?> clazz,
			final int index) throws IllegalArgumentException,
			IndexOutOfBoundsException {
		return getGenericClass(clazz, new GenericClassGetter() {
			public Class<?> getGenericClass(ParameterizedType parameterizedType) {
				Type[] params = parameterizedType.getActualTypeArguments();
				if (index >= params.length || index < 0) {
					throw new IndexOutOfBoundsException("Index: " + index+ ", Size of Parameterized Type: " + params.length);
				}

				if (!(params[index] instanceof Class)) {
					throw new IllegalArgumentException("The class must be Parameterized");
				}

				Class<?> genericClass = (Class<?>) params[index];
				if (LOGGER.isInfoEnabled()) {
					LOGGER.info("It is found the " + index+ "th generic class in class [" + clazz.getName()+ "]");
				}
				return genericClass;
			}
		});
	}

//	public static Class<?> getGenericClass(final Class<?> clazz, final String name) {
//		return getGenericClass(clazz, new GenericClassGetter() {
//			@SuppressWarnings("unchecked")
//			public Class<?> getGenericClass(ParameterizedType parameterizedType) {
//				TypeVariable[] typeParameters = ((Class) parameterizedType.getRawType()).getTypeParameters();
//				for (int i = 0; i< typeParameters.length; i++) {
//					if (name.equals(typeParameters[i].getName())) {
//						return (Class<?>) parameterizedType.getActualTypeArguments()[i];
//					}
//				}
//				return null;
//			}
//		});
//	}

	private static Class<?> getGenericClass(final Class<?> clazz,
			GenericClassGetter getter) {
		Assert.notNull(clazz);

		Class<?> superClass = clazz;
		Type genType = clazz.getGenericSuperclass();

		while (!(genType instanceof ParameterizedType)) {
			superClass = superClass.getSuperclass();
			if (superClass == null || superClass == Object.class) {
				break;
			}

			genType = superClass.getGenericSuperclass();
		}

		if (!(genType instanceof ParameterizedType)) {
			Type[] genTypes = clazz.getGenericInterfaces();
			for (Type type : genTypes) {
				if (type instanceof ParameterizedType) {
					genType = type;
				}
			}
		}

		if (!(genType instanceof ParameterizedType)) {
			throw new IllegalArgumentException(
					"The class must be Parameterized");
		}

		ParameterizedType parameterizedType = ((ParameterizedType) genType);
		if (LOGGER.isInfoEnabled()) {
			LOGGER.info("It will find actual type argument from type["
					+ parameterizedType + "]");
		}

		return getter.getGenericClass(parameterizedType);
	}

	private static interface GenericClassGetter {
		Class<?> getGenericClass(ParameterizedType type);
	}


//	public static Class<?> getReturnGenericType(Class<?> targetClass, Method method) {
//			return GenericCollectionTypeResolver
//					.getCollectionReturnType(method);
//	} 
//
//	public static Class<?>[] getParameterGenericTypes(Class<?> targetClass,
//			Method targetMethod) {
//		Class<?>[] parameterTypes = targetMethod.getParameterTypes();
//		Class<?>[] parameterTargetTypes = new Class<?>[parameterTypes.length];
//		for (int i = 0; i < parameterTypes.length; i++) {
//			MethodParameter methodParam = new MethodParameter(targetMethod, i);
//			if (Collection.class.isAssignableFrom(parameterTypes[i])) {
//				parameterTargetTypes[i] = GenericCollectionTypeResolver
//						.getCollectionParameterType(methodParam);
//			}
//		}
//		return parameterTargetTypes;
//	}
}
